import java.awt.BorderLayout;
import java.awt.ComponentOrientation;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.border.TitledBorder;

/**
 * Klasa modelujaca interfejs graficzny uzytkownika.
 * 
 * @author Ewa Byra (nr indeksu s6405)
 */
@SuppressWarnings("serial")
public class GUI extends JFrame {
	private List<Integer> set;
	private JLabel setDataLabel;
	private JLabel populationSizeLabel;
	private JLabel generationCountLabel;
	private JLabel crossoverParamLabel;
	private JLabel mutationParamLabel;
	private JLabel selectionMethodLabel;
	private JLabel beamLabel;
	private JTextField populationSizeInputField;
	private JTextField generationCountInputField;
	private JTextField crossoverParamInputField;
	private JTextField mutationParamInputField;
	private JTextField beamInputField;
	private JTextField setDataInputField;
	private JButton generateNewDataButton;
	private JButton runProgramButton;
	private JComboBox<String> selectionMethodChoice;
	private JPanel geneticAlgorithmPanel;
	private JPanel multiHillClimbingPanel;
	private JPanel inputDataPanel;
	private JPanel buttonPanel;
	private JPanel mainPanel;
	private String errorMessage;

	/**
	 * Kostruktor klasy. Inicjalizuje zmienne, dodaje akcje do przyciskow,
	 * dodaje komponenty do paneli.
	 */
	public GUI() {
		setTitle("Ewa Byra (s6405)");
		setSize(600, 220);

		this.errorMessage = "Wystapil blad z nastepujacymi danymi wejsciowymi:";

		this.set = Generator.generateData(6);
		String inputData = "" + set;

		/* Etykiety */
		this.setDataLabel = new JLabel("Dane wejsciowe:");
		this.populationSizeLabel = new JLabel("Ilosc osobnikow w populacji");
		this.generationCountLabel = new JLabel("Liczba generacji");
		this.crossoverParamLabel = new JLabel("Parametr krzyzowania");
		this.mutationParamLabel = new JLabel("Parametr mutacji");
		this.beamLabel = new JLabel("Ilosc sciezek");
		this.selectionMethodLabel = new JLabel("Metoda selekcji");

		/* Pola tekstowe */
		this.setDataInputField = new JTextField(inputData.substring(1,
				inputData.length() - 1));
		this.populationSizeInputField = new JTextField("10");
		this.generationCountInputField = new JTextField("10");
		this.crossoverParamInputField = new JTextField("0.5");
		this.mutationParamInputField = new JTextField("0.5");
		this.beamInputField = new JTextField("2");

		this.setDataInputField.setPreferredSize(new Dimension(160, 20));
		this.populationSizeInputField.setPreferredSize(new Dimension(60, 20));
		this.generationCountInputField.setPreferredSize(new Dimension(60, 20));
		this.crossoverParamInputField.setPreferredSize(new Dimension(60, 20));
		this.mutationParamInputField.setPreferredSize(new Dimension(60, 20));
		this.beamInputField.setPreferredSize(new Dimension(60, 20));

		/* Lista wyboru metody selekcji chromosomow */
		String[] petStrings = { "Metoda kola ruletki",
				"Metoda selekcji turniejowej" };
		this.selectionMethodChoice = new JComboBox<String>(petStrings);

		/* Przyciski */
		this.generateNewDataButton = new JButton("Generuj dane wejsciowe");
		this.runProgramButton = new JButton("Uruchom");

		this.generateNewDataButton.addActionListener(generateDataAction());
		this.runProgramButton.addActionListener(runProgramAction());

		/* Panele */
		this.geneticAlgorithmPanel = new JPanel(new FlowLayout());
		this.multiHillClimbingPanel = new JPanel();
		this.inputDataPanel = new JPanel();
		this.buttonPanel = new JPanel();
		this.mainPanel = new JPanel(new BorderLayout());

		TitledBorder titleMultiHillClimbing = BorderFactory
				.createTitledBorder("Algorytm wspinaczki");
		TitledBorder titleGeneric = BorderFactory
				.createTitledBorder("Algorytm generyczny");

		titleMultiHillClimbing.setTitleJustification(TitledBorder.LEFT);
		titleGeneric.setTitleJustification(TitledBorder.LEFT);

		this.multiHillClimbingPanel.setBorder(titleMultiHillClimbing);
		this.geneticAlgorithmPanel.setBorder(titleGeneric);

		this.geneticAlgorithmPanel.add(populationSizeLabel);
		this.geneticAlgorithmPanel.add(populationSizeInputField);
		this.geneticAlgorithmPanel.add(generationCountLabel);
		this.geneticAlgorithmPanel.add(generationCountInputField);
		this.geneticAlgorithmPanel.add(crossoverParamLabel);
		this.geneticAlgorithmPanel.add(crossoverParamInputField);
		this.geneticAlgorithmPanel.add(mutationParamLabel);
		this.geneticAlgorithmPanel.add(mutationParamInputField);
		this.geneticAlgorithmPanel.add(selectionMethodLabel);
		this.geneticAlgorithmPanel.add(selectionMethodChoice);

		this.geneticAlgorithmPanel
				.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);

		this.multiHillClimbingPanel.add(beamLabel);
		this.multiHillClimbingPanel.add(beamInputField);

		this.inputDataPanel.add(setDataLabel);
		this.inputDataPanel.add(setDataInputField);
		this.inputDataPanel.add(generateNewDataButton);

		this.buttonPanel.add(runProgramButton);

		this.mainPanel.add(inputDataPanel, BorderLayout.PAGE_START);
		this.mainPanel.add(geneticAlgorithmPanel, BorderLayout.CENTER);
		this.mainPanel.add(multiHillClimbingPanel, BorderLayout.WEST);
		this.mainPanel.add(buttonPanel, BorderLayout.PAGE_END);

		add(mainPanel);

	}

	/**
	 * Metoda wywolana przyciskiem 'Generuj dane we'. Pobiera od uzytkownika
	 * wartosc wielkosci zbioru, a nastepnie generuje dane wejsciowe.<br>
	 * Gdy wprowadzona wartosc okreslajaca wielkosc zbioru jest wieksza od 10
	 * pokazuje sie informacja, ze program moze dzialac dluzej.<br>
	 * 
	 * @return Akcje dla przycisku generateNewDataButton.
	 */
	private ActionListener generateDataAction() {
		ActionListener al = new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent arg0) {
				String setSizeText = JOptionPane.showInputDialog(null,
						"Podaj ilosc elementow zbioru");

				try {
					int setSize = Integer.parseInt(setSizeText);
					boolean generateData = true;
					if (setSize > 10) {
						generateData = false;
						int answer = JOptionPane
								.showConfirmDialog(null,
										"Uwaga! Program moze nie dzialac prawidlowo, czy chcesz kontynuowac?");
						if (answer == JOptionPane.YES_OPTION)
							generateData = true;
					}
					if (generateData)
						setSet(Generator.generateData(setSize));
				} catch (Exception e) {

				}

			}
		};
		return al;
	}

	/**
	 * Metoda wywolana przyciskiem 'Uruchom'. Metoda wywoluje algorytm
	 * wspinaczki i algorytm genetyczny na podstawie wprowadzonych parametrow, a
	 * nastepnie wyswietla wyniki.
	 * 
	 * @return Akcje dla przycisku runProgramButton.
	 */
	private ActionListener runProgramAction() {
		ActionListener al = new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent arg0) {
				String score = "";
				Partition resultGA, resultMHC, resultGATMP, resultMHCTMP;

				try {
					int testCount = Integer.parseInt(JOptionPane.showInputDialog(null, "Podaj ilosc testow:"));

					GeneticAlgorithm ga = new GeneticAlgorithm(getSet(),
							getPopulationSize());
					MultiHillClimbingAlgorithm mhc = new MultiHillClimbingAlgorithm(
							getSet());
					
					resultGA = ga.runAlgorithm(getGenerationCount(),
							getCrossoverParam(), getMutationParam(),
							getSelectionMethodChoice());
					resultMHC = mhc.runAlgorithm(getBeam());
					
					for (int i = 1; i < testCount; i++) {
						resultGATMP = ga.runAlgorithm(getGenerationCount(),
								getCrossoverParam(), getMutationParam(),
								getSelectionMethodChoice());
						resultMHCTMP = mhc.runAlgorithm(getBeam());
						
						if(resultGA.getFit() < resultGATMP.getFit()) {
							resultGA = resultGATMP;
						}
						
						if(resultMHC.getFit() < resultMHCTMP.getFit()) {
							resultMHC = resultMHCTMP;
						}
					}

					if (resultGA.getFit() < resultMHC.getFit()) {
						score += "Lepszy wynik podal alorytm genetyczny\n"
								+ resultGA + "\nAlgorytm wspinaczki podal:\n"
								+ resultMHC;
					} else if (resultGA.getFit() > resultMHC.getFit()) {
						score += "Lepszy wynik podal algorytm wspinaczki\n"
								+ resultMHC + "\nAlgorytm genetyczny podal:\n"
								+ resultGA;
					} else {
						score += "Oba algorytmy podaly ten sam wynik:\nAlgorytm genetyczny: "
								+ resultGA
								+ "\nAlgorytm wspinaczki: "
								+ resultMHC + "\n";
					}

					JOptionPane.showMessageDialog(null, score, "Wynik",
							JOptionPane.INFORMATION_MESSAGE);
				} catch (Exception e) {
					JOptionPane.showMessageDialog(null, getErrorMessage(),
							"Blad", JOptionPane.ERROR_MESSAGE);
				}
			}
		};
		return al;
	}

	/**
	 * Metoda zmienia dane wejsciowe. Uzywana gdy generowane sa nowe dane
	 * wejsciowe w metodzie generateDataAction().
	 * 
	 * @param set
	 *            : lista elementow zbioru.
	 */
	public void setSet(List<Integer> set) {
		this.set = set;
		this.setDataInputField.setText(("" + set).substring(1,
				("" + set).length() - 1));
	}

	/**
	 * Metoda zwraca dane wejsciowe - instancje problemu podzialu zbioru. Metoda
	 * wywolywana przy inicjalizacji obiektow klas {@link GeneticAlgorithm} i
	 * {@link MultiHillClimbingAlgorithm} w metodzie runProgramAction().
	 * 
	 * @return Zbior elementow stanowiacy instancje problemu.
	 */
	public List<Integer> getSet() {
		String inputData = this.setDataInputField.getText();
		String[] numbers = inputData.split(", ");
		int setElement = 0;
		boolean isError = false;
		List<Integer> tempSet = new ArrayList<>();

		for (String number : numbers) {
			try {
				setElement = Integer.parseInt(number);
				tempSet.add(setElement);
			} catch (Exception e) {
				isError = true;
				break;
			}
		}

		if (isError) {
			setSet(set);
			JOptionPane
					.showMessageDialog(
							null,
							"Podano bledne dane wejsciowe, dane nie zostaly zmienione.",
							"Blad", JOptionPane.ERROR_MESSAGE);
		} else {
			set.clear();
			set.addAll(tempSet);
		}

		return set;
	}

	/**
	 * Metoda zwraca wielkosc populacji. Metoda wywolywana przy inicjalizacji
	 * obiektu klasy {@link GeneticAlgorithm}w metodzie runProgramAction().
	 * 
	 * @return Wielkosc populacji.
	 */
	public int getPopulationSize() {
		int result = 0;
		try {
			result = Integer.parseInt(populationSizeInputField.getText());
		} catch (Exception e) {
			this.errorMessage += "\n- Wielkoscia populacji";
		}
		return result;
	}

	/**
	 * Metoda zwraca ilosc generacji. Metoda wywolywana przy wywolywaniu metody
	 * runAlgorithm() klasy {@link GeneticAlgorithm}w metodzie
	 * runProgramAction().
	 * 
	 * @return Ilosc generacji.
	 */
	public int getGenerationCount() {
		int result = 0;
		try {
			result = Integer.parseInt(generationCountInputField.getText());
		} catch (Exception e) {
			this.errorMessage += "\n- Ilością generacji";
		}
		return result;
	}

	/**
	 * Metoda zwraca parametr krzyzowania. Metoda wywolywana przy wywolywaniu
	 * metody runAlgorithm() klasy {@link GeneticAlgorithm}w metodzie
	 * runProgramAction().
	 * 
	 * @return Parametr krzyzowania.
	 */
	public double getCrossoverParam() {
		double result = 0;
		try {
			result = Double.parseDouble(crossoverParamInputField.getText());
		} catch (Exception e) {
			this.errorMessage += "\n- Parametrem krzyzowania";
		}
		return result;
	}

	/**
	 * Metoda zwraca parametr mutacji. Metoda wywolywana przy wywolywaniu metody
	 * runAlgorithm() klasy {@link GeneticAlgorithm}w metodzie
	 * runProgramAction().
	 * 
	 * @return Parametr mutacji.
	 */
	public double getMutationParam() {
		double result = 0;
		try {
			result = Double.parseDouble(crossoverParamInputField.getText());
		} catch (Exception e) {
			this.errorMessage += "\n- Paramentrem mutacji";
		}
		return result;
	}

	/**
	 * Metoda zwraca wybrana przez uzytkowanika metode selekcji. Metoda
	 * wywolywana przy wywolywaniu metody runAlgorithm() klasy
	 * {@link GeneticAlgorithm}w metodzie runProgramAction().
	 * 
	 * @return Metoda selekcji chromosomow.
	 */
	public int getSelectionMethodChoice() {
		return selectionMethodChoice.getSelectedIndex();
	}

	/**
	 * Metoda zwraca ilosc scierzek. Metoda wywolywana przy wywolywaniu metody
	 * runAlgorithm() klasy {@link MultiHillClimbingAlgorithm}w metodzie
	 * runProgramAction().
	 * 
	 * @return Ilosc scierzek.
	 */
	public int getBeam() {
		int result = 0;
		try {
			result = Integer.parseInt(beamInputField.getText());
		} catch (Exception e) {
			this.errorMessage += "\n- Iloscia scierzek";
		}
		return result;
	}

	/**
	 * Metoda uzywana do zwracania informacji, w ktorych danych wejsciowych
	 * wystapil blad.
	 * 
	 * @return Wiadomosc, w ktorych danych wejsciowych wystapil blad.
	 */
	public String getErrorMessage() {
		return this.errorMessage;
	}
}
